library(tidyverse)
library(rvest)
library(progress)
library(lubridate)
library(tidytext)
library(plotly)
# Reading in pages and extracting hyperlinks
result <- vector("list",10)
for(i in 0:9) {
webpage <- read_html(paste0("https://www.metacritic.com/browse/games/release-date/available/ps4/metascore?page=", i))
loop_links <- webpage %>% html_nodes("#main .product_title a") %>% html_attr('href')
loop_links_proper <- paste0("https://www.metacritic.com", loop_links)
result[[i+1]] <- loop_links_proper
}
# Joining results into a single vector
all_links <- unlist(result)
#game_page <- read_html("https://www.metacritic.com/game/playstation-4/wolfenstein-ii-the-new-colossus")
Before performing the loop, I first need to figure out how to store the results. I see that there are multiple ways to go about referring to the elements of a loop. One of them is naming the i itself the variable and the results list as well. The other way is leaving a numbered list, indices between 1 and the length, and referring to the name as myvector[i]. I’ll try the first method.
I can continue it after Naruto
# Getting all the attributes
final_result <- vector("list", 1812)
names(final_result) <- all_links
for(i in all_links) {
#game_page <- read_html("https://www.metacritic.com/game/playstation-4/wolfenstein-ii-the-new-colossus")
game_page <- read_html(i)
title <- game_page %>% html_nodes("h1") %>% html_text()
release_date <- game_page %>% html_nodes(".release_data .data") %>% html_text()
developer <- game_page %>% html_nodes(".developer .data") %>% html_text()
publisher <- game_page %>% html_nodes(".publisher a") %>% html_text()
genre <- game_page %>% html_nodes(".product_genre .data") %>% html_text()
score <- game_page %>% html_nodes(".positive span, .mixed span, .negative span") %>% html_text()
review_count <- game_page %>% html_nodes(".highlight_metascore .count a") %>% html_text()
description <- game_page %>% html_nodes(".data .blurb_expanded") %>% html_text()
title <- ifelse(length(title) == 0, "Notapplicable", title)
release_date <- ifelse(length(release_date) == 0, "Notapplicable", release_date)
developer <- ifelse(length(developer) == 0, "Notapplicable", developer)
publisher <- ifelse(length(publisher) == 0, "Notapplicable", publisher)
genre <- ifelse(length(genre) == 0, "Notapplicable", genre)
score <- ifelse(length(score) == 0, "Notapplicable", score)
review_count <- ifelse(length(review_count) == 0, "Notapplicable", review_count)
description <- ifelse(length(description) == 0, "Notapplicable", description)
genre <- paste(genre, collapse = ', ')
# Creating table entry from vectors
final_result[[i]] <- data.frame(title, release_date, developer, publisher, genre, score, review_count, description)
print(score)
}
temp_results <- final_result
The loop got stopped at the 14th iteration. This means the scraping process is really fast as it only took around 30s to scrape 14 games.
I got a bit stuck. I’m creating this data frame with each iteration, but when I get an empty value, I can’t assemble my data frame. I need a way to check if my vector is empty, and if it is, need to put an “NA” as the value.
I really need to invest in a progress bar for my loops. I have no way of knowing how far along I am. Since my indices are character vectors, I would need to name them as indices and refer to the position in my vector from my index. I’m gonna implement progress bars next time because this is a pain in the ass.
This is an OK way to track progress, it prints the score so I can track how far along it is. I will use progress bars next time.
Another thing to consider is skipping iterations if the for loop fails. In this case, I got a 404 error opening a site. Need to skip these errors.
Let’s carry on with the temp result.
dataset <- do.call("rbind", temp_results)
The script is not perfect as it truncates the genre to a single value, something to look out for. Also, the description is taken as a single scraped category, which leaves a lot of values empty. I’m fairly happy with this overall. Time to clean up the data.
696 of the 1607 games have missing descriptions, which is 43%. Not ideal but workable
dataset$title <- as.character(dataset$title)
dataset$release_date <- as.character(dataset$release_date)
dataset$release_date <- mdy(dataset$release_date)
dataset$developer <- as.character(dataset$developer)
dataset$publisher <- as.character(dataset$publisher)
dataset$genre <- as.character(dataset$genre)
dataset$score <- as.numeric(as.character(dataset$score))
NAs introduced by coercion
dataset$review_count <- as.character(dataset$review_count)
dataset$review_count <- gsub(" Critics", "", dataset$review_count)
dataset$review_count <- as.numeric(dataset$review_count)
dataset$description <- as.character(dataset$description)
Alright, the dataset is pretty tidy with variables in the right format. I’m quite happy with this.
Questions:
Which studio produced the most games?
dataset %>% count(developer) %>% arrange(desc(n))
Interestingly, Telltale games comes first. This is due to the episodic nature of their games.
Who makes the best games?
dataset %>% group_by(developer) %>% summarise(mean_score = mean(score), median_score = median(score), n = n()) %>% arrange(desc(mean_score))
Which genre receives the highest scores?
dataset %>% group_by(genre) %>% summarise(mean_score = mean(score), median_score = median(score), n = n()) %>% arrange(desc(median_score))
# ggplot(aes(x = genre, y = median_score)) +
# geom_bar(stat = "identity") +
# ggtitle("Title") +
# coord_flip()
The most interesting part would be taking out the nondescipt titles, breaking down into words, score association and finding the words that are best correlated with high scores.
dataset %>% count(description) %>% arrange(desc(n))
stripped_dataset <- dataset %>% filter(description != "Notapplicable")
So far so good, now I just need to unnest the words.
words <- stripped_dataset %>% unnest_tokens(word, description)
words %>% group_by(word) %>% summarise(mean_score = mean(score), median_review_count = median(review_count), count = n()) %>% filter(count >= 30) %>% arrange(desc(mean_score))
Classic seems to have a higher average score, 80 games scoring in this category. Full has a really high score too. Overall, it is quite hard to distinguish between words associated with a single game and trends, but upon visual inspection, I noticed that the trends are very slightly observable.
This dataset is currently pretty good for checking the works of different developers and publishers and ranking them based on the quality of their titles.
However, the dataset is currently difficult to use for searching games to play due to the restricted genre categories and the incomplete descriptions.
Besides, the Metacritic Website has a pretty extensive search tool that can narrow down games to genres with extensive arranging by several categories.
dataset %>% ggplot(aes(x = score)) +
geom_density() +
ggtitle("Title")

We can see the cutoff at around 56 points where the 404 error was encountered. Nonetheless, this is a pretty complete picture. The distribution looks very normal with a mean of around 72.5 scores.
dataset %>% ggplot(aes(x = review_count)) +
geom_density() +
ggtitle("Title")

The review count on the other hand shows a very strong right skew with a maximum of around 8 reviews.
Let’s investigate how review count and scores are associated.
ggplotly(
dataset %>% ggplot(aes(x = review_count, y = score, label = title)) +
geom_jitter(alpha = 0.2) +
ggtitle("Title")
)
Up until the review count of around 70, I don’t see a correlation between the number of reviews and score. However, as we go above the count of 75, the scores are beginning to get higher. These are most likely high profile, high quality AAA titles.
ggplotly(
dataset %>% ggplot(aes(x = release_date)) +
geom_density() +
ggtitle("Title")
)
dataset %>% ggplot(aes(x = release_date, y = score)) +
geom_jitter() +
ggtitle("Title")

Let’s define really good games by Metascores of above 85. Let’s see how many games there are with this criteria over the years to assess how well gaming is doing.
dataset %>% mutate(year = year(release_date), month = month(release_date)) %>%
ggplot(aes(x = as.factor(year), y = score, fill = as.factor(year))) +
geom_boxplot() +
ggtitle("Title")

It looks like gaming has gone downhill since 2013 in terms of the quality of the games, assuming metacritic didn’t change its scoring methodology.
dataset %>% mutate(year = year(release_date), month = month(release_date)) %>%
ggplot(aes(x = as.factor(month), y = score)) +
geom_boxplot() +
ggtitle("Title")

The quality of games is somewhat seasonal.
dataset %>% mutate(year = year(release_date), month = month(release_date)) %>%
ggplot(aes(x = as.factor(year), y = review_count)) +
geom_boxplot() +
ggtitle("Title")

Nothing really pops out.
Let’s rank the years
It does look like a downward tends, however the review count changed considerably as well.
LS0tDQp0aXRsZTogIk1ldGFjcml0aWMgU2NyYXBpbmciDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkocHJvZ3Jlc3MpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQpgYGB7cn0NCiMgUmVhZGluZyBpbiBwYWdlcyBhbmQgZXh0cmFjdGluZyBoeXBlcmxpbmtzDQpyZXN1bHQgPC0gdmVjdG9yKCJsaXN0IiwxMCkNCg0KZm9yKGkgaW4gMDo5KSB7DQogIHdlYnBhZ2UgPC0gcmVhZF9odG1sKHBhc3RlMCgiaHR0cHM6Ly93d3cubWV0YWNyaXRpYy5jb20vYnJvd3NlL2dhbWVzL3JlbGVhc2UtZGF0ZS9hdmFpbGFibGUvcHM0L21ldGFzY29yZT9wYWdlPSIsIGkpKQ0KICBsb29wX2xpbmtzIDwtIHdlYnBhZ2UgJT4lIGh0bWxfbm9kZXMoIiNtYWluIC5wcm9kdWN0X3RpdGxlIGEiKSAlPiUgaHRtbF9hdHRyKCdocmVmJykNCiAgbG9vcF9saW5rc19wcm9wZXIgPC0gcGFzdGUwKCJodHRwczovL3d3dy5tZXRhY3JpdGljLmNvbSIsIGxvb3BfbGlua3MpDQogIHJlc3VsdFtbaSsxXV0gPC0gbG9vcF9saW5rc19wcm9wZXINCn0NCg0KIyBKb2luaW5nIHJlc3VsdHMgaW50byBhIHNpbmdsZSB2ZWN0b3INCmFsbF9saW5rcyA8LSB1bmxpc3QocmVzdWx0KQ0KYGBgDQoNCmBgYHtyfQ0KI2dhbWVfcGFnZSA8LSByZWFkX2h0bWwoImh0dHBzOi8vd3d3Lm1ldGFjcml0aWMuY29tL2dhbWUvcGxheXN0YXRpb24tNC93b2xmZW5zdGVpbi1paS10aGUtbmV3LWNvbG9zc3VzIikNCmBgYA0KDQpCZWZvcmUgcGVyZm9ybWluZyB0aGUgbG9vcCwgSSBmaXJzdCBuZWVkIHRvIGZpZ3VyZSBvdXQgaG93IHRvIHN0b3JlIHRoZSByZXN1bHRzLiBJIHNlZSB0aGF0IHRoZXJlIGFyZSBtdWx0aXBsZSB3YXlzIHRvIGdvIGFib3V0IHJlZmVycmluZyB0byB0aGUgZWxlbWVudHMgb2YgYSBsb29wLiBPbmUgb2YgdGhlbSBpcyBuYW1pbmcgdGhlIGkgaXRzZWxmIHRoZSB2YXJpYWJsZSBhbmQgdGhlIHJlc3VsdHMgbGlzdCBhcyB3ZWxsLiBUaGUgb3RoZXIgd2F5IGlzIGxlYXZpbmcgYSBudW1iZXJlZCBsaXN0LCBpbmRpY2VzIGJldHdlZW4gMSBhbmQgdGhlIGxlbmd0aCwgYW5kIHJlZmVycmluZyB0byB0aGUgbmFtZSBhcyBteXZlY3RvcltpXS4gSSdsbCB0cnkgdGhlIGZpcnN0IG1ldGhvZC4NCg0KSSBjYW4gY29udGludWUgaXQgYWZ0ZXIgTmFydXRvDQoNCmBgYHtyfQ0KIyBHZXR0aW5nIGFsbCB0aGUgYXR0cmlidXRlcw0KDQpmaW5hbF9yZXN1bHQgPC0gdmVjdG9yKCJsaXN0IiwgMTgxMikNCm5hbWVzKGZpbmFsX3Jlc3VsdCkgPC0gYWxsX2xpbmtzDQoNCmZvcihpIGluIGFsbF9saW5rcykgew0KI2dhbWVfcGFnZSA8LSByZWFkX2h0bWwoImh0dHBzOi8vd3d3Lm1ldGFjcml0aWMuY29tL2dhbWUvcGxheXN0YXRpb24tNC93b2xmZW5zdGVpbi1paS10aGUtbmV3LWNvbG9zc3VzIikNCmdhbWVfcGFnZSA8LSByZWFkX2h0bWwoaSkNCg0KdGl0bGUgPC0gZ2FtZV9wYWdlICU+JSBodG1sX25vZGVzKCJoMSIpICU+JSBodG1sX3RleHQoKQ0KcmVsZWFzZV9kYXRlIDwtIGdhbWVfcGFnZSAlPiUgaHRtbF9ub2RlcygiLnJlbGVhc2VfZGF0YSAuZGF0YSIpICU+JSBodG1sX3RleHQoKQ0KZGV2ZWxvcGVyIDwtIGdhbWVfcGFnZSAlPiUgaHRtbF9ub2RlcygiLmRldmVsb3BlciAuZGF0YSIpICU+JSBodG1sX3RleHQoKQ0KcHVibGlzaGVyIDwtIGdhbWVfcGFnZSAlPiUgaHRtbF9ub2RlcygiLnB1Ymxpc2hlciBhIikgJT4lIGh0bWxfdGV4dCgpDQpnZW5yZSA8LSBnYW1lX3BhZ2UgJT4lIGh0bWxfbm9kZXMoIi5wcm9kdWN0X2dlbnJlIC5kYXRhIikgJT4lIGh0bWxfdGV4dCgpDQpzY29yZSA8LSBnYW1lX3BhZ2UgJT4lIGh0bWxfbm9kZXMoIi5wb3NpdGl2ZSBzcGFuLCAubWl4ZWQgc3BhbiwgLm5lZ2F0aXZlIHNwYW4iKSAlPiUgaHRtbF90ZXh0KCkNCnJldmlld19jb3VudCA8LSBnYW1lX3BhZ2UgJT4lIGh0bWxfbm9kZXMoIi5oaWdobGlnaHRfbWV0YXNjb3JlIC5jb3VudCBhIikgJT4lIGh0bWxfdGV4dCgpDQpkZXNjcmlwdGlvbiA8LSBnYW1lX3BhZ2UgJT4lIGh0bWxfbm9kZXMoIi5kYXRhIC5ibHVyYl9leHBhbmRlZCIpICU+JSBodG1sX3RleHQoKQ0KDQp0aXRsZSA8LSBpZmVsc2UobGVuZ3RoKHRpdGxlKSA9PSAwLCAiTm90YXBwbGljYWJsZSIsIHRpdGxlKQ0KcmVsZWFzZV9kYXRlIDwtIGlmZWxzZShsZW5ndGgocmVsZWFzZV9kYXRlKSA9PSAwLCAiTm90YXBwbGljYWJsZSIsIHJlbGVhc2VfZGF0ZSkNCmRldmVsb3BlciA8LSBpZmVsc2UobGVuZ3RoKGRldmVsb3BlcikgPT0gMCwgIk5vdGFwcGxpY2FibGUiLCBkZXZlbG9wZXIpDQpwdWJsaXNoZXIgPC0gaWZlbHNlKGxlbmd0aChwdWJsaXNoZXIpID09IDAsICJOb3RhcHBsaWNhYmxlIiwgcHVibGlzaGVyKQ0KZ2VucmUgPC0gaWZlbHNlKGxlbmd0aChnZW5yZSkgPT0gMCwgIk5vdGFwcGxpY2FibGUiLCBnZW5yZSkNCnNjb3JlIDwtIGlmZWxzZShsZW5ndGgoc2NvcmUpID09IDAsICJOb3RhcHBsaWNhYmxlIiwgc2NvcmUpDQpyZXZpZXdfY291bnQgPC0gaWZlbHNlKGxlbmd0aChyZXZpZXdfY291bnQpID09IDAsICJOb3RhcHBsaWNhYmxlIiwgcmV2aWV3X2NvdW50KQ0KZGVzY3JpcHRpb24gPC0gaWZlbHNlKGxlbmd0aChkZXNjcmlwdGlvbikgPT0gMCwgIk5vdGFwcGxpY2FibGUiLCBkZXNjcmlwdGlvbikNCg0KZ2VucmUgPC0gcGFzdGUoZ2VucmUsIGNvbGxhcHNlID0gJywgJykNCg0KIyBDcmVhdGluZyB0YWJsZSBlbnRyeSBmcm9tIHZlY3RvcnMNCmZpbmFsX3Jlc3VsdFtbaV1dIDwtIGRhdGEuZnJhbWUodGl0bGUsIHJlbGVhc2VfZGF0ZSwgZGV2ZWxvcGVyLCBwdWJsaXNoZXIsIGdlbnJlLCBzY29yZSwgcmV2aWV3X2NvdW50LCBkZXNjcmlwdGlvbikNCg0KcHJpbnQoc2NvcmUpDQoNCn0NCmBgYA0KDQpgYGB7cn0NCnRlbXBfcmVzdWx0cyA8LSBmaW5hbF9yZXN1bHQNCmBgYA0KDQoNClRoZSBsb29wIGdvdCBzdG9wcGVkIGF0IHRoZSAxNHRoIGl0ZXJhdGlvbi4gVGhpcyBtZWFucyB0aGUgc2NyYXBpbmcgcHJvY2VzcyBpcyByZWFsbHkgZmFzdCBhcyBpdCBvbmx5IHRvb2sgYXJvdW5kIDMwcyB0byBzY3JhcGUgMTQgZ2FtZXMuDQoNCkkgZ290IGEgYml0IHN0dWNrLiBJJ20gY3JlYXRpbmcgdGhpcyBkYXRhIGZyYW1lIHdpdGggZWFjaCBpdGVyYXRpb24sIGJ1dCB3aGVuIEkgZ2V0IGFuIGVtcHR5IHZhbHVlLCBJIGNhbid0IGFzc2VtYmxlIG15IGRhdGEgZnJhbWUuIEkgbmVlZCBhIHdheSB0byBjaGVjayBpZiBteSB2ZWN0b3IgaXMgZW1wdHksIGFuZCBpZiBpdCBpcywgbmVlZCB0byBwdXQgYW4gIk5BIiBhcyB0aGUgdmFsdWUuDQoNCkkgcmVhbGx5IG5lZWQgdG8gaW52ZXN0IGluIGEgcHJvZ3Jlc3MgYmFyIGZvciBteSBsb29wcy4gSSBoYXZlIG5vIHdheSBvZiBrbm93aW5nIGhvdyBmYXIgYWxvbmcgSSBhbS4gU2luY2UgbXkgaW5kaWNlcyBhcmUgY2hhcmFjdGVyIHZlY3RvcnMsIEkgd291bGQgbmVlZCB0byBuYW1lIHRoZW0gYXMgaW5kaWNlcyBhbmQgcmVmZXIgdG8gdGhlIHBvc2l0aW9uIGluIG15IHZlY3RvciBmcm9tIG15IGluZGV4LiBJJ20gZ29ubmEgaW1wbGVtZW50IHByb2dyZXNzIGJhcnMgbmV4dCB0aW1lIGJlY2F1c2UgdGhpcyBpcyBhIHBhaW4gaW4gdGhlIGFzcy4NCg0KVGhpcyBpcyBhbiBPSyB3YXkgdG8gdHJhY2sgcHJvZ3Jlc3MsIGl0IHByaW50cyB0aGUgc2NvcmUgc28gSSBjYW4gdHJhY2sgaG93IGZhciBhbG9uZyBpdCBpcy4gSSB3aWxsIHVzZSBwcm9ncmVzcyBiYXJzIG5leHQgdGltZS4NCg0KQW5vdGhlciB0aGluZyB0byBjb25zaWRlciBpcyBza2lwcGluZyBpdGVyYXRpb25zIGlmIHRoZSBmb3IgbG9vcCBmYWlscy4gSW4gdGhpcyBjYXNlLCBJIGdvdCBhIDQwNCBlcnJvciBvcGVuaW5nIGEgc2l0ZS4gTmVlZCB0byBza2lwIHRoZXNlIGVycm9ycy4NCg0KTGV0J3MgY2Fycnkgb24gd2l0aCB0aGUgdGVtcCByZXN1bHQuDQogDQoNCmBgYHtyfQ0KZGF0YXNldCA8LSBkby5jYWxsKCJyYmluZCIsIHRlbXBfcmVzdWx0cykNCmBgYA0KDQpUaGUgc2NyaXB0IGlzIG5vdCBwZXJmZWN0IGFzIGl0IHRydW5jYXRlcyB0aGUgZ2VucmUgdG8gYSBzaW5nbGUgdmFsdWUsIHNvbWV0aGluZyB0byBsb29rIG91dCBmb3IuIEFsc28sIHRoZSBkZXNjcmlwdGlvbiBpcyB0YWtlbiBhcyBhIHNpbmdsZSBzY3JhcGVkIGNhdGVnb3J5LCB3aGljaCBsZWF2ZXMgYSBsb3Qgb2YgdmFsdWVzIGVtcHR5LiBJJ20gZmFpcmx5IGhhcHB5IHdpdGggdGhpcyBvdmVyYWxsLiBUaW1lIHRvIGNsZWFuIHVwIHRoZSBkYXRhLg0KDQo2OTYgb2YgdGhlIDE2MDcgZ2FtZXMgaGF2ZSBtaXNzaW5nIGRlc2NyaXB0aW9ucywgd2hpY2ggaXMgNDMlLiBOb3QgaWRlYWwgYnV0IHdvcmthYmxlDQoNCmBgYHtyfQ0KZGF0YXNldCR0aXRsZSA8LSBhcy5jaGFyYWN0ZXIoZGF0YXNldCR0aXRsZSkNCmRhdGFzZXQkcmVsZWFzZV9kYXRlIDwtIGFzLmNoYXJhY3RlcihkYXRhc2V0JHJlbGVhc2VfZGF0ZSkNCmRhdGFzZXQkcmVsZWFzZV9kYXRlIDwtIG1keShkYXRhc2V0JHJlbGVhc2VfZGF0ZSkNCmRhdGFzZXQkZGV2ZWxvcGVyIDwtIGFzLmNoYXJhY3RlcihkYXRhc2V0JGRldmVsb3BlcikNCmRhdGFzZXQkcHVibGlzaGVyIDwtIGFzLmNoYXJhY3RlcihkYXRhc2V0JHB1Ymxpc2hlcikNCmRhdGFzZXQkZ2VucmUgPC0gYXMuY2hhcmFjdGVyKGRhdGFzZXQkZ2VucmUpDQpkYXRhc2V0JHNjb3JlIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRhdGFzZXQkc2NvcmUpKQ0KZGF0YXNldCRyZXZpZXdfY291bnQgPC0gYXMuY2hhcmFjdGVyKGRhdGFzZXQkcmV2aWV3X2NvdW50KQ0KZGF0YXNldCRyZXZpZXdfY291bnQgPC0gZ3N1YigiIENyaXRpY3MiLCAiIiwgZGF0YXNldCRyZXZpZXdfY291bnQpDQpkYXRhc2V0JHJldmlld19jb3VudCA8LSBhcy5udW1lcmljKGRhdGFzZXQkcmV2aWV3X2NvdW50KQ0KZGF0YXNldCRkZXNjcmlwdGlvbiA8LSBhcy5jaGFyYWN0ZXIoZGF0YXNldCRkZXNjcmlwdGlvbikNCmBgYA0KDQpBbHJpZ2h0LCB0aGUgZGF0YXNldCBpcyBwcmV0dHkgdGlkeSB3aXRoIHZhcmlhYmxlcyBpbiB0aGUgcmlnaHQgZm9ybWF0LiBJJ20gcXVpdGUgaGFwcHkgd2l0aCB0aGlzLg0KDQpRdWVzdGlvbnM6IA0KDQpXaGljaCBzdHVkaW8gcHJvZHVjZWQgdGhlIG1vc3QgZ2FtZXM/DQoNCmBgYHtyfQ0KZGF0YXNldCAlPiUgY291bnQoZGV2ZWxvcGVyKSAlPiUgYXJyYW5nZShkZXNjKG4pKQ0KYGBgDQpJbnRlcmVzdGluZ2x5LCBUZWxsdGFsZSBnYW1lcyBjb21lcyBmaXJzdC4gVGhpcyBpcyBkdWUgdG8gdGhlIGVwaXNvZGljIG5hdHVyZSBvZiB0aGVpciBnYW1lcy4NCg0KV2hvIG1ha2VzIHRoZSBiZXN0IGdhbWVzPw0KDQpgYGB7cn0NCmRhdGFzZXQgJT4lIGdyb3VwX2J5KGRldmVsb3BlcikgJT4lIHN1bW1hcmlzZShtZWFuX3Njb3JlID0gbWVhbihzY29yZSksIG1lZGlhbl9zY29yZSA9IG1lZGlhbihzY29yZSksIG4gPSBuKCkpICU+JSBhcnJhbmdlKGRlc2MobWVhbl9zY29yZSkpDQpgYGANCg0KV2hpY2ggZ2VucmUgcmVjZWl2ZXMgdGhlIGhpZ2hlc3Qgc2NvcmVzPw0KDQpgYGB7cn0NCmRhdGFzZXQgJT4lIGdyb3VwX2J5KGdlbnJlKSAlPiUgc3VtbWFyaXNlKG1lYW5fc2NvcmUgPSBtZWFuKHNjb3JlKSwgbWVkaWFuX3Njb3JlID0gbWVkaWFuKHNjb3JlKSwgbiA9IG4oKSkgJT4lIGFycmFuZ2UoZGVzYyhtZWRpYW5fc2NvcmUpKSANCg0KIyAgZ2dwbG90KGFlcyh4ID0gZ2VucmUsIHkgPSBtZWRpYW5fc2NvcmUpKSArDQojICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQojICAgIGdndGl0bGUoIlRpdGxlIikgKw0KIyAgICBjb29yZF9mbGlwKCkNCmBgYA0KDQpUaGUgbW9zdCBpbnRlcmVzdGluZyBwYXJ0IHdvdWxkIGJlIHRha2luZyBvdXQgdGhlIG5vbmRlc2NpcHQgdGl0bGVzLCBicmVha2luZyBkb3duIGludG8gd29yZHMsIHNjb3JlIGFzc29jaWF0aW9uIGFuZCBmaW5kaW5nIHRoZSB3b3JkcyB0aGF0IGFyZSBiZXN0IGNvcnJlbGF0ZWQgd2l0aCBoaWdoIHNjb3Jlcy4NCg0KYGBge3J9DQpkYXRhc2V0ICU+JSBjb3VudChkZXNjcmlwdGlvbikgJT4lIGFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KDQoNCmBgYHtyfQ0Kc3RyaXBwZWRfZGF0YXNldCA8LSBkYXRhc2V0ICU+JSBmaWx0ZXIoZGVzY3JpcHRpb24gIT0gIk5vdGFwcGxpY2FibGUiKQ0KYGBgDQoNClNvIGZhciBzbyBnb29kLCBub3cgSSBqdXN0IG5lZWQgdG8gdW5uZXN0IHRoZSB3b3Jkcy4NCg0KYGBge3J9DQp3b3JkcyA8LSBzdHJpcHBlZF9kYXRhc2V0ICU+JSB1bm5lc3RfdG9rZW5zKHdvcmQsIGRlc2NyaXB0aW9uKQ0KYGBgDQoNCmBgYHtyfQ0Kd29yZHMgJT4lIGdyb3VwX2J5KHdvcmQpICU+JSBzdW1tYXJpc2UobWVhbl9zY29yZSA9IG1lYW4oc2NvcmUpLCBtZWRpYW5fcmV2aWV3X2NvdW50ID0gbWVkaWFuKHJldmlld19jb3VudCksIGNvdW50ID0gbigpKSAlPiUgZmlsdGVyKGNvdW50ID49IDMwKSAlPiUgYXJyYW5nZShkZXNjKG1lYW5fc2NvcmUpKQ0KYGBgDQoNCkNsYXNzaWMgc2VlbXMgdG8gaGF2ZSBhIGhpZ2hlciBhdmVyYWdlIHNjb3JlLCA4MCBnYW1lcyBzY29yaW5nIGluIHRoaXMgY2F0ZWdvcnkuIEZ1bGwgaGFzIGEgcmVhbGx5IGhpZ2ggc2NvcmUgdG9vLiBPdmVyYWxsLCBpdCBpcyBxdWl0ZSBoYXJkIHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gd29yZHMgYXNzb2NpYXRlZCB3aXRoIGEgc2luZ2xlIGdhbWUgYW5kIHRyZW5kcywgYnV0IHVwb24gdmlzdWFsIGluc3BlY3Rpb24sIEkgbm90aWNlZCB0aGF0IHRoZSB0cmVuZHMgYXJlIHZlcnkgc2xpZ2h0bHkgb2JzZXJ2YWJsZS4gDQoNClRoaXMgZGF0YXNldCBpcyBjdXJyZW50bHkgcHJldHR5IGdvb2QgZm9yIGNoZWNraW5nIHRoZSB3b3JrcyBvZiBkaWZmZXJlbnQgZGV2ZWxvcGVycyBhbmQgcHVibGlzaGVycyBhbmQgcmFua2luZyB0aGVtIGJhc2VkIG9uIHRoZSBxdWFsaXR5IG9mIHRoZWlyIHRpdGxlcy4NCg0KSG93ZXZlciwgdGhlIGRhdGFzZXQgaXMgY3VycmVudGx5IGRpZmZpY3VsdCB0byB1c2UgZm9yIHNlYXJjaGluZyBnYW1lcyB0byBwbGF5IGR1ZSB0byB0aGUgcmVzdHJpY3RlZCBnZW5yZSBjYXRlZ29yaWVzIGFuZCB0aGUgaW5jb21wbGV0ZSBkZXNjcmlwdGlvbnMuIA0KDQpCZXNpZGVzLCB0aGUgTWV0YWNyaXRpYyBXZWJzaXRlIGhhcyBhIHByZXR0eSBleHRlbnNpdmUgc2VhcmNoIHRvb2wgdGhhdCBjYW4gbmFycm93IGRvd24gZ2FtZXMgdG8gZ2VucmVzIHdpdGggZXh0ZW5zaXZlIGFycmFuZ2luZyBieSBzZXZlcmFsIGNhdGVnb3JpZXMuDQoNCmBgYHtyfQ0KZGF0YXNldCAlPiUgZ2dwbG90KGFlcyh4ID0gc2NvcmUpKSArDQogIGdlb21fZGVuc2l0eSgpICsNCiAgZ2d0aXRsZSgiVGl0bGUiKQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIGN1dG9mZiBhdCBhcm91bmQgNTYgcG9pbnRzIHdoZXJlIHRoZSA0MDQgZXJyb3Igd2FzIGVuY291bnRlcmVkLiBOb25ldGhlbGVzcywgdGhpcyBpcyBhIHByZXR0eSBjb21wbGV0ZSBwaWN0dXJlLiBUaGUgZGlzdHJpYnV0aW9uIGxvb2tzIHZlcnkgbm9ybWFsIHdpdGggYSBtZWFuIG9mIGFyb3VuZCA3Mi41IHNjb3Jlcy4NCg0KYGBge3J9DQpkYXRhc2V0ICU+JSBnZ3Bsb3QoYWVzKHggPSByZXZpZXdfY291bnQpKSArDQogIGdlb21fZGVuc2l0eSgpICsNCiAgZ2d0aXRsZSgiVGl0bGUiKQ0KYGBgDQoNClRoZSByZXZpZXcgY291bnQgb24gdGhlIG90aGVyIGhhbmQgc2hvd3MgYSB2ZXJ5IHN0cm9uZyByaWdodCBza2V3IHdpdGggYSBtYXhpbXVtIG9mIGFyb3VuZCA4IHJldmlld3MuDQoNCkxldCdzIGludmVzdGlnYXRlIGhvdyByZXZpZXcgY291bnQgYW5kIHNjb3JlcyBhcmUgYXNzb2NpYXRlZC4NCg0KYGBge3J9DQpnZ3Bsb3RseSgNCmRhdGFzZXQgJT4lIGdncGxvdChhZXMoeCA9IHJldmlld19jb3VudCwgeSA9IHNjb3JlLCBsYWJlbCA9IHRpdGxlKSkgKw0KICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMikgKw0KICBnZ3RpdGxlKCJUaXRsZSIpDQopDQpgYGANCg0KVXAgdW50aWwgdGhlIHJldmlldyBjb3VudCBvZiBhcm91bmQgNzAsIEkgZG9uJ3Qgc2VlIGEgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgbnVtYmVyIG9mIHJldmlld3MgYW5kIHNjb3JlLiBIb3dldmVyLCBhcyB3ZSBnbyBhYm92ZSB0aGUgY291bnQgb2YgNzUsIHRoZSBzY29yZXMgYXJlIGJlZ2lubmluZyB0byBnZXQgaGlnaGVyLiBUaGVzZSBhcmUgbW9zdCBsaWtlbHkgaGlnaCBwcm9maWxlLCBoaWdoIHF1YWxpdHkgQUFBIHRpdGxlcy4NCg0KYGBge3J9DQpnZ3Bsb3RseSgNCmRhdGFzZXQgJT4lIGdncGxvdChhZXMoeCA9IHJlbGVhc2VfZGF0ZSkpICsNCiAgZ2VvbV9kZW5zaXR5KCkgKw0KICBnZ3RpdGxlKCJUaXRsZSIpDQopDQpgYGANCg0KYGBge3J9DQpkYXRhc2V0ICU+JSBnZ3Bsb3QoYWVzKHggPSByZWxlYXNlX2RhdGUsIHkgPSBzY29yZSkpICsNCiAgZ2VvbV9qaXR0ZXIoKSArDQogIGdndGl0bGUoIlRpdGxlIikNCmBgYA0KDQpMZXQncyBkZWZpbmUgcmVhbGx5IGdvb2QgZ2FtZXMgYnkgTWV0YXNjb3JlcyBvZiBhYm92ZSA4NS4gTGV0J3Mgc2VlIGhvdyBtYW55IGdhbWVzIHRoZXJlIGFyZSB3aXRoIHRoaXMgY3JpdGVyaWEgb3ZlciB0aGUgeWVhcnMgdG8gYXNzZXNzIGhvdyB3ZWxsIGdhbWluZyBpcyBkb2luZy4NCg0KYGBge3J9DQpkYXRhc2V0ICU+JSBtdXRhdGUoeWVhciA9IHllYXIocmVsZWFzZV9kYXRlKSwgbW9udGggPSBtb250aChyZWxlYXNlX2RhdGUpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcih5ZWFyKSwgeSA9IHNjb3JlLCBmaWxsID0gYXMuZmFjdG9yKHllYXIpKSkgKw0KICAgIGdlb21fYm94cGxvdCgpICsNCiAgICBnZ3RpdGxlKCJUaXRsZSIpDQpgYGANCg0KSXQgbG9va3MgbGlrZSBnYW1pbmcgaGFzIGdvbmUgZG93bmhpbGwgc2luY2UgMjAxMyBpbiB0ZXJtcyBvZiB0aGUgcXVhbGl0eSBvZiB0aGUgZ2FtZXMsIGFzc3VtaW5nIG1ldGFjcml0aWMgZGlkbid0IGNoYW5nZSBpdHMgc2NvcmluZyBtZXRob2RvbG9neS4NCg0KYGBge3J9DQpkYXRhc2V0ICU+JSBtdXRhdGUoeWVhciA9IHllYXIocmVsZWFzZV9kYXRlKSwgbW9udGggPSBtb250aChyZWxlYXNlX2RhdGUpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihtb250aCksIHkgPSBzY29yZSkpICsNCiAgICBnZW9tX2JveHBsb3QoKSArDQogICAgZ2d0aXRsZSgiVGl0bGUiKQ0KYGBgDQoNClRoZSBxdWFsaXR5IG9mIGdhbWVzIGlzIHNvbWV3aGF0IHNlYXNvbmFsLg0KDQpgYGB7cn0NCmRhdGFzZXQgJT4lIG11dGF0ZSh5ZWFyID0geWVhcihyZWxlYXNlX2RhdGUpLCBtb250aCA9IG1vbnRoKHJlbGVhc2VfZGF0ZSkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKHllYXIpLCB5ID0gcmV2aWV3X2NvdW50KSkgKw0KICAgIGdlb21fYm94cGxvdCgpICsNCiAgICBnZ3RpdGxlKCJUaXRsZSIpDQpgYGANCg0KTm90aGluZyByZWFsbHkgcG9wcyBvdXQuDQoNCkxldCdzIHJhbmsgdGhlIHllYXJzDQpgYGB7cn0NCmRhdGFzZXQgPC0gbmEub21pdChkYXRhc2V0KQ0KDQpkYXRhc2V0ICU+JSBtdXRhdGUoeWVhciA9IHllYXIocmVsZWFzZV9kYXRlKSwgbW9udGggPSBtb250aChyZWxlYXNlX2RhdGUpKSAlPiUgZmlsdGVyKCkgJT4lIGdyb3VwX2J5KHllYXIpICU+JSBzdW1tYXJpc2UobWVhbl9zY29yZSA9IG1lYW4oc2NvcmUpLCBjb3VudCA9IG4oKSkNCmBgYA0KDQpJdCBkb2VzIGxvb2sgbGlrZSBhIGRvd253YXJkIHRlbmRzLCBob3dldmVyIHRoZSByZXZpZXcgY291bnQgY2hhbmdlZCBjb25zaWRlcmFibHkgYXMgd2VsbC4=